<template>
    <div>
        <!-- 页面标题 -->
        <h2>算法分类思维导图</h2>
        <!-- 图表容器，使用ref引用，设置宽高和背景色 -->
        <div ref="container" style="width:100%;height:600px;background:#f5f5f5;"></div>
    </div>
</template>

<script setup lang="ts">
// 导入Vue3的响应式引用和生命周期钩子
import { ref, onMounted } from 'vue';
// 导入G6图表库的核心组件和工具函数
import { Graph, treeToGraphData, register, Rect, ExtensionCategory, CommonEvent, Badge } from '@antv/g6';

// 创建容器引用，用于获取DOM元素
const container = ref();

// 定义折叠按钮的颜色常量
const GREY_COLOR = '#CED4D9';

// 注册自定义方形节点类，继承自G6的Rect类
class CustomRectNode extends Rect {
    // 获取当前节点的数据
    get data() {
        return this.context.model.getNodeLikeDatum(this.id);
    }

    // 获取当前节点的子节点数据
    get childrenData() {
        return this.context.model.getChildrenData(this.id);
    }

    // 获取折叠按钮的样式配置
    getCollapseStyle(attributes: any) {
        // 如果没有子节点，不显示折叠按钮
        if (this.childrenData.length === 0) return false;
        // 获取折叠状态
        const { collapsed } = attributes;
        // 获取节点尺寸
        const [width, height] = this.getSize(attributes);
        // 返回折叠按钮的样式对象
        return {
            backgroundFill: '#fff', // 背景填充色
            backgroundHeight: 16, // 背景高度
            backgroundLineWidth: 1, // 背景边框宽度
            backgroundRadius: 0, // 背景圆角
            backgroundStroke: GREY_COLOR, // 背景边框色
            backgroundWidth: 16, // 背景宽度
            cursor: 'pointer' as const, // 鼠标指针样式
            fill: GREY_COLOR, // 填充色
            fontSize: 16, // 字体大小
            text: collapsed ? '+' : '-', // 根据折叠状态显示+或-
            textAlign: 'middle' as const, // 文本水平对齐
            textBaseline: 'middle' as const, // 文本垂直对齐
            x: width / 2, // X坐标位置（节点右侧）
            y: 0, // Y坐标位置（节点中心）
        };
    }

    // 绘制折叠按钮
    drawCollapseShape(attributes: any, container: any) {
        // 获取折叠按钮样式
        const collapseStyle = this.getCollapseStyle(attributes);
        // 创建或更新折叠按钮
        const btn = this.upsert('collapse', Badge, collapseStyle, container);

        // 如果按钮存在且未绑定事件，则绑定点击事件
        if (btn && !Reflect.has(btn, '__bind__')) {
            // 标记按钮已绑定事件
            Reflect.set(btn, '__bind__', true);
            // 添加点击事件监听器
            btn.addEventListener(CommonEvent.CLICK, () => {
                // 获取当前折叠状态
                const { collapsed } = this.attributes;
                // 获取图表实例
                const graph = this.context.graph;
                // 根据当前状态执行展开或折叠操作
                if (collapsed) graph.expandElement(this.id, {
                    animation: false,
                });
                else graph.collapseElement(this.id, {
                    animation: false,
                });
            });
        }
    }

    // 渲染节点
    render(attributes: any, container: any) {
        // 调用父类的渲染方法
        super.render(attributes, container);
        // 绘制折叠按钮
        this.drawCollapseShape(attributes, container);
    }
    
    // 定义锚点位置，确保边连接到节点的左侧或右侧中间位置
    getAnchorPoints() {
        return [
            [0, 0.5], // 左侧中间锚点
            [1, 0.5], // 右侧中间锚点
        ];
    }
}
// 注册自定义节点类型
register(ExtensionCategory.NODE, 'custom-rect', CustomRectNode);

// 组件挂载后的初始化函数
onMounted(() => {
    // 从远程API获取算法分类数据
    fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
        .then((res) => res.json()) // 将响应转换为JSON格式
        .then((data) => {
            // 将树形数据转换为G6需要的图数据格式
            const { nodes = [], edges } = treeToGraphData(data);
            
            // 在控制台输出节点和边的数据，用于调试
            console.log('Nodes:', nodes);
            console.log('Edges:', edges);

            // 为边添加锚点配置，确保边连接到节点的左右两端
            if (edges) {
                // 遍历所有边
                edges.forEach((edge: any) => {
                    // 思维导图中，父节点的边从右侧出发，子节点的边从左侧进入
                    edge.sourceAnchor = 0; // 源节点使用右侧端口 (ports[0] = right)
                    edge.targetAnchor = 1; // 目标节点使用左侧端口 (ports[1] = left)
                });
            }

            // 遍历所有节点，动态计算每个节点的宽度
            nodes.forEach(node => {
                const fontSize = 16; // 字体大小
                const padding = 32; // 内边距
                const text = node.id || ''; // 获取节点文本
                // 创建canvas上下文用于测量文本宽度
                const ctx = document.createElement('canvas').getContext('2d');
                if (!ctx) return; // 如果无法获取上下文，则跳过
                // 设置字体样式
                ctx.font = `${fontSize}px sans-serif`;
                // 测量文本宽度
                const textWidth = ctx.measureText(text as string).width;
                // 计算节点宽度，最小宽度为80px
                const width = Math.max(80, textWidth + padding);
                // 初始化节点样式对象
                node.style = node.style || {};
                // 设置节点宽度
                node.style.width = width;
                // 设置节点高度
                node.style.height = 40;
                // 设置节点尺寸数组
                node.style.size = [width - 40, 25];
            });

            // 创建G6图表实例
            const graph = new Graph({
                container: container.value, // 容器元素
                autoFit: 'view', // 自动适应视图
                data: { nodes, edges }, // 图表数据
                node: {
                    type: 'custom-rect', // 使用自定义节点类型
                    style: {
                        labelText: (d: any) => d.id, // 标签文本内容
                        labelBackground: false, // 不显示标签背景
                        labelPlacement: 'center', // 标签居中放置
                        labelTextAlign: 'center', // 标签文本水平居中
                        labelTextBaseline: 'middle', // 标签文本垂直居中
                        fontSize: 16, // 字体大小
                        padding: 8, // 内边距
                        stroke: '#409EFF', // 边框颜色
                        fill: '#1890ff', // 填充颜色
                        labelTextFill: '#fff', // 标签文本颜色
                        radius: 6, // 圆角半径
                        cursor: 'pointer', // 鼠标指针样式
                        ports: [{ placement: 'right' }, { placement: 'left' }], // 定义左右两个端口
                    },
                    animation: {
                        enter: false, // 禁用进入动画
                    },
                    state: {
                        collapsed: {
                            stroke: '#a084ff', // 折叠状态的边框颜色
                            fill: '#f6f6ff', // 折叠状态的填充颜色
                        }
                    }
                },
                edge: {
                    type: 'cubic-horizontal', // 水平贝塞尔曲线边类型
                    style: {
                        stroke: '#b3b3b3', // 边的颜色
                        lineWidth: 1.5, // 边的宽度
                        endArrow: false, // 不显示箭头
                    },
                    animation: {
                        enter: false, // 禁用进入动画
                    },
                },
                layout: {
                    type: 'mindmap', // 思维导图布局类型
                    direction: 'H', // 水平方向
                    getHeight: () => 40, // 节点高度
                    getWidth: () => 40, // 节点宽度
                    getVGap: () => 16, // 垂直间距
                    getHGap: () => 100, // 水平间距
                },
                behaviors: ['collapse-expand', 'drag-canvas', 'zoom-canvas', 'drag-element'], // 交互行为
            });

            // 渲染图表
            graph.render();
        })
        .catch((error) => {
            // 错误处理：在控制台输出错误信息
            console.error('Failed to fetch data:', error);
        });
});
</script>

<style scoped>
/* 标题样式 */
h2 {
    margin-bottom: 16px; /* 下边距 */
}
</style>
